/*-------------------------------------------------------------------------
 *
 * spgist_private.h
 *	  SP-GiST 访问方法的私有声明。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/access/spgist_private.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef SPGIST_PRIVATE_H
#define SPGIST_PRIVATE_H

#include "access/itup.h"
#include "access/spgist.h"
#include "catalog/pg_am_d.h"
#include "nodes/tidbitmap.h"
#include "storage/buf.h"
#include "utils/geo_decls.h"
#include "utils/relcache.h"


typedef struct SpGistOptions
{
	int32		varlena_header_;	/* varlena 头部（请勿直接触摸！） */
	int			fillfactor;		/* 页面填充因子百分比（0..100） */
} SpGistOptions;

#define SpGistGetFillFactor(relation) \
	(AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \
				 relation->rd_rel->relam == SPGIST_AM_OID), \
	 (relation)->rd_options ? \
	 ((SpGistOptions *) (relation)->rd_options)->fillfactor : \
	 SPGIST_DEFAULT_FILLFACTOR)
#define SpGistGetTargetPageFreeSpace(relation) \
	(BLCKSZ * (100 - SpGistGetFillFactor(relation)) / 100)


/* SPGiST 叶元组有一个键列，选择性地包含列 */
#define spgKeyColumn 0
#define spgFirstIncludeColumn 1

/* 固定位置页面的页面编号 */
#define SPGIST_METAPAGE_BLKNO	 (0)	/* metapage */
#define SPGIST_ROOT_BLKNO		 (1)	/* 普通条目的根 */
#define SPGIST_NULL_BLKNO		 (2)	/* 空值条目的根 */
#define SPGIST_LAST_FIXED_BLKNO  SPGIST_NULL_BLKNO

#define SpGistBlockIsRoot(blkno) \
	((blkno) == SPGIST_ROOT_BLKNO || (blkno) == SPGIST_NULL_BLKNO)
#define SpGistBlockIsFixed(blkno) \
	((BlockNumber) (blkno) <= (BlockNumber) SPGIST_LAST_FIXED_BLKNO)

/*
 * SPGiST 索引页面上特殊空间的内容
 */
typedef struct SpGistPageOpaqueData
{
	uint16		flags;			/* 见下面的位定义 */
	uint16		nRedirection;	/* 页面上的重定向元组数量 */
	uint16		nPlaceholder;	/* 页面上的占位符元组数量 */
	/* 注意，既没有 LIVE 也没有 DEAD 元组的计数... */
	uint16		spgist_page_id; /* 用于识别 SP-GiST 索引 */
} SpGistPageOpaqueData;

typedef SpGistPageOpaqueData *SpGistPageOpaque;

/* 页面特殊空间中的标志位 */
#define SPGIST_META			(1<<0)
#define SPGIST_DELETED		(1<<1)	/* 从不设置，但保留以保持向后兼容性 */
#define SPGIST_LEAF			(1<<2)
#define SPGIST_NULLS		(1<<3)

#define SpGistPageGetOpaque(page) ((SpGistPageOpaque) PageGetSpecialPointer(page))
#define SpGistPageIsMeta(page) (SpGistPageGetOpaque(page)->flags & SPGIST_META)
#define SpGistPageIsDeleted(page) (SpGistPageGetOpaque(page)->flags & SPGIST_DELETED)
#define SpGistPageIsLeaf(page) (SpGistPageGetOpaque(page)->flags & SPGIST_LEAF)
#define SpGistPageStoresNulls(page) (SpGistPageGetOpaque(page)->flags & SPGIST_NULLS)

/*
 * 页面 ID 是为了方便 pg_filedump 和类似工具，它们将很难区分不同索引类型的页面。
 * 它应该是页面上的最后 2 个字节。由于对齐考虑，这在某种程度上是“免费的”。
 *
 * 见上面的 GinPageOpaqueData 注释。
 */
#define SPGIST_PAGE_ID		0xFF82

/*
 * 每个后端在其 index->rd_amcache 区域中保留最后使用的页面信息缓存。
 * 这从索引元页面的共享存储初始化，并偶尔写回。
 */
typedef struct SpGistLastUsedPage
{
	BlockNumber blkno;			/* 块号，或 InvalidBlockNumber */
	int			freeSpace;		/* 页面的空闲空间（可能已经过时！） */
} SpGistLastUsedPage;

/* 注意：cachedPage[] 中的索引与 SpGistGetBuffer 的标志分配相匹配 */
#define SPGIST_CACHED_PAGES 8

typedef struct SpGistLUPCache
{
	SpGistLastUsedPage cachedPage[SPGIST_CACHED_PAGES];
} SpGistLUPCache;

/*
 * 元页面
 */
typedef struct SpGistMetaPageData
{
	uint32		magicNumber;	/* 用于身份交叉检查 */
	SpGistLUPCache lastUsedPages;	/* 最后使用信息的共享存储 */
} SpGistMetaPageData;

#define SPGIST_MAGIC_NUMBER (0xBA0BABEE)

#define SpGistPageGetMeta(p) \
	((SpGistMetaPageData *) PageGetContents(p))

/*
 * 索引 AM 的私有状态。SpGistState 对插入和搜索代码是通用的；
 * SpGistScanOpaque 仅用于搜索。
 */

typedef struct SpGistLeafTupleData *SpGistLeafTuple;	/* 前向引用 */

/* 每种数据类型信息在 SpGistState 中所需 */
typedef struct SpGistTypeDesc
{
	Oid			type;
	int16		attlen;
	bool		attbyval;
	char		attalign;
	char		attstorage;
} SpGistTypeDesc;

typedef struct SpGistState
{
	Relation	index;			/* 我们正在处理的索引 */

	spgConfigOut config;		/* 由 opclass 配置方法填充 */

	SpGistTypeDesc attType;		/* 要索引/恢复的值类型 */
	SpGistTypeDesc attLeafType; /* 叶元组值的类型 */
	SpGistTypeDesc attPrefixType;	/* 内元组前缀值的类型 */
	SpGistTypeDesc attLabelType;	/* 节点标签值的类型 */

	/* leafTupDesc 通常指向索引的 tupdesc，但并不总是如此 */
	TupleDesc	leafTupDesc;	/* 叶级元组的描述符 */

	char	   *deadTupleStorage;	/* spgFormDeadTuple 的工作空间 */

	TransactionId myXid;		/* 创建重定向元组时使用的 XID */
	bool		isBuild;		/* 如果正在进行索引构建，则为 true */
} SpGistState;

/* 在搜索期间稍后需要重新检查的项 */
typedef struct SpGistSearchItem
{
	pairingheap_node phNode;	/* 配对堆节点 */
	Datum		value;			/* 从父节点重建的值，或如果是叶子则为 leafValue */
	SpGistLeafTuple leafTuple;	/* 整个叶元组，如果需要的话 */
	void	   *traversalValue; /* opclass特定的遍历值 */
	int			level;			/* 此页面上项目的层级 */
	ItemPointerData heapPtr;	/* 堆信息，如果是堆元组 */
	bool		isNull;			/* SearchItem 是 NULL 项 */
	bool		isLeaf;			/* SearchItem 是堆项目 */
	bool		recheck;		/* 需要重新检查条件 */
	bool		recheckDistances;	/* 需要重新检查距离 */

	/* 包含 numberOfOrderBys 条目的数组 */
	double		distances[FLEXIBLE_ARRAY_MEMBER];
} SpGistSearchItem;

#define SizeOfSpGistSearchItem(n_distances) \
	(offsetof(SpGistSearchItem, distances) + sizeof(double) * (n_distances))

/*
 * 索引扫描的私有状态
 */
typedef struct SpGistScanOpaqueData
{
	SpGistState state;			/* 详见上文 */
	pairingheap *scanQueue;		/* 待访问项的队列 */
	MemoryContext tempCxt;		/* 短期内存上下文 */
	MemoryContext traversalCxt; /* 单次扫描生命周期内存上下文 */

	/* 控制标志，指示是否搜索 NULL 和/或 非 NULL */
	bool		searchNulls;	/* 扫描匹配（所有） NULL 条目 */
	bool		searchNonNulls; /* 扫描匹配（某些）非 NULL 条目 */

	/* 要传递给 opclass 的索引条件（删除了与 NULL 相关的条件） */
	int			numberOfKeys;	/* 索引限定条件数量 */
	ScanKey		keyData;		/* 索引限定符描述符数组 */
	int			numberOfOrderBys;	/* 排序操作符数量 */
	int			numberOfNonNullOrderBys;	/* 有序操作符的数量
											 * 以及非空参数 */
	ScanKey		orderByData;	/* 排序操作符描述符数组 */
	Oid		   *orderByTypes;	/* 有序操作符返回类型的数组 */
	int		   *nonNullOrderByOffsets;	/* 原始数组中非空
										 * 有序键的偏移量数组 */
	Oid			indexCollation; /* 索引列的排序规则 */

	/* Opclass 定义的函数： */
	FmgrInfo	innerConsistentFn;
	FmgrInfo	leafConsistentFn;

	/* 预分配的工作区数组： */
	double	   *zeroDistances;
	double	   *infDistances;

	/* 这些字段仅在 amgetbitmap 扫描中使用： */
	TIDBitmap  *tbm;			/* 正在填充的位图 */
	int64		ntids;			/* 传递给位图的 TID 数量 */

	/* 这些字段仅在 amgettuple 扫描中使用： */
	bool		want_itup;		/* 我们是否在重建元组？ */
	TupleDesc	reconTupDesc;	/* 如果是，重建元组的描述符 */
	int			nPtrs;			/* 当前页面上找到的 TID 数量 */
	int			iPtr;			/* 索引用于遍历相同内容 */
	ItemPointerData heapPtrs[MaxIndexTuplesPerPage];	/* 来自当前页面的 TID */
	bool		recheck[MaxIndexTuplesPerPage]; /* 他们的重检标志 */
	bool		recheckDistances[MaxIndexTuplesPerPage];	/* 距离重检
															 * 标志 */
	HeapTuple	reconTups[MaxIndexTuplesPerPage];	/* 重建的元组 */

	/* 距离（用于重检） */
	IndexOrderByDistance *distances[MaxIndexTuplesPerPage];

	/*
	 * 注意：使用 MaxIndexTuplesPerPage 以上的做法有点怪，因为
	 * SpGistLeafTuples 并不完全是 IndexTuples；然而，它们的大小更大，
	 * 所以这是安全的。
	 */
} SpGistScanOpaqueData;

typedef SpGistScanOpaqueData *SpGistScanOpaque;

/*
 * 这个结构是我们实际上保存在 index->rd_amcache 中的。它包含
 * 静态配置信息以及 lastUsedPages 缓存。
 */
typedef struct SpGistCache
{
	spgConfigOut config;		/* 由 opclass 配置方法填充 */

	SpGistTypeDesc attType;		/* 要索引/恢复的值类型 */
	SpGistTypeDesc attLeafType; /* 叶元组值的类型 */
	SpGistTypeDesc attPrefixType;	/* 内元组前缀值的类型 */
	SpGistTypeDesc attLabelType;	/* 节点标签值的类型 */

	SpGistLUPCache lastUsedPages;	/* 最近使用信息的本地存储 */
} SpGistCache;


/*
 * SPGiST 元组类型。注意：内部、叶子和死元组结构
 * 必须在相同的位置具有相同的 tupstate 字段！真正的内部和
 * 叶子元组的 tupstate 总是为 LIVE；如果状态是其他值，
 * 使用 SpGistDeadTuple 结构来检查元组。
 */

/* tupstate 的值（更多信息见 README） */
#define SPGIST_LIVE			0	/* 正常的活动元组（内部或叶子） */
#define SPGIST_REDIRECT		1	/* 临时重定向占位符 */
#define SPGIST_DEAD			2	/* 死亡，因链接无法移除 */
#define SPGIST_PLACEHOLDER	3	/* 占位符，用于保留偏移量 */

/*
 * SPGiST 内部元组：划分一组元组的“节点”列表
 *
 * 内部元组布局：
 * 头部/可选前缀/节点数组，这些是 SpGistNodeTuples
 *
 * size 和 prefixSize 必须是 MAXALIGN 的倍数
 *
 * 如果前缀数据是按值传递的类型，那么它以 Datum 表示形式存储，
 * 也就是说，它的磁盘表示形式的长度为 sizeof(Datum)。这是一个相当不幸的选择，
 * 因为在其他地方 Postgres 并没有使用 Datum 作为磁盘表示；这在
 * 32 位与 64 位构建之间造成了不必要的不兼容性。但这种兼容性损失大部分是理论上的，
 * 因为 MAXIMUM_ALIGNOF 通常在这些构建中也不同。无论如何，我们现在被困在这一点上。
 */
typedef struct SpGistInnerTupleData
{
	unsigned int tupstate:2,	/* 活动/重定向/死亡/占位符 */
				allTheSame:1,	/* 元组中的所有节点都是等价的 */
				nNodes:13,		/* 内部元组中的节点数量 */
				prefixSize:16;	/* 前缀的大小，如果没有则为 0 */
	uint16		size;			/* 内部元组的总大小 */
	/* 在大多数机器上，这里会有几个浪费的字节 */
	/* 前缀数据随之而来，然后是节点 */
} SpGistInnerTupleData;

typedef SpGistInnerTupleData *SpGistInnerTuple;

/* 这些必须与上面声明的位域中适合的最大值匹配 */
#define SGITMAXNNODES		0x1FFF
#define SGITMAXPREFIXSIZE	0xFFFF
#define SGITMAXSIZE			0xFFFF

#define SGITHDRSZ			MAXALIGN(sizeof(SpGistInnerTupleData))
#define _SGITDATA(x)		(((char *) (x)) + SGITHDRSZ)
#define SGITDATAPTR(x)		((x)->prefixSize ? _SGITDATA(x) : NULL)
#define SGITDATUM(x, s)		((x)->prefixSize ? \
							 ((s)->attPrefixType.attbyval ? \
							  *(Datum *) _SGITDATA(x) : \
							  PointerGetDatum(_SGITDATA(x))) \
							 : (Datum) 0)
#define SGITNODEPTR(x)		((SpGistNodeTuple) (_SGITDATA(x) + (x)->prefixSize))

/* 遍历内部元组节点的宏 */
#define SGITITERATE(x, i, nt)	\
	for ((i) = 0, (nt) = SGITNODEPTR(x); \
		 (i) < (x)->nNodes; \
		 (i)++, (nt) = (SpGistNodeTuple) (((char *) (nt)) + IndexTupleSize(nt)))

/*
 * SPGiST 节点元组：内部元组中的一个节点
 *
 * 节点元组使用与普通 Postgres IndexTuples 相同的头部，
 * 但我们不使用空位图，因为我们知道只有一列，
 * 所以 INDEX_NULL_MASK 位就足够了。此外，按值传递的数据项
 * 以 Datum 形式存储，与内部元组前缀的约定相同。
 */

typedef IndexTupleData SpGistNodeTupleData;

typedef SpGistNodeTupleData *SpGistNodeTuple;

#define SGNTHDRSZ			MAXALIGN(sizeof(SpGistNodeTupleData))
#define SGNTDATAPTR(x)		(((char *) (x)) + SGNTHDRSZ)
#define SGNTDATUM(x, s)		((s)->attLabelType.attbyval ? \
							 *(Datum *) SGNTDATAPTR(x) : \
							 PointerGetDatum(SGNTDATAPTR(x)))

/*
 * SPGiST 叶子元组：携带叶子数据和堆元组 TID，
 * 并可选地包含一些“包含的”列。
 *
 * 在最简单的情况下，叶子数据与索引值相同；
 * 但它也可能是后缀或其他某种增量，这允许
 * 给定了解从前缀路径到达这里的知识进行重建。
 * 所有包含的列均未经修改进行存储。
 *
 * 如果存在包含的列并且任何数据项为 NULL，则会出现 NULL 位图。
 * 对于没有包含列的 NULL 叶子数据的情况，我们不需要 NULL 位图，
 * 因为我们可以通过检查元组是否存储在 NULL 页面上来推断叶子
 * 数据是否为 NULL。（这一规定主要是为了向后兼容，但确实节省了
 * 32 位机器上的空间。）与其他 PG 索引元组设计一样，如果 NULL
 * 位图存在，则无论实际属性数量如何，它的大小为 INDEX_MAX_KEYS 位。
 * 对于通常的 INDEX_MAX_KEYS 选择，由于对齐因素，这不会带来任何成本。
 *
 * 大小字段比存储在磁盘上的叶子元组可能需要的要宽，
 * 但这使我们能够形成叶子元组，即使数据项过宽以至于无法直接存储，
 * 并且由于对齐因素，这没有成本。
 *
 * t_info 保存 nextOffset 字段（宽 14 位，足够用于支持的
 * 页面大小）加上 has-nulls-bitmap 标志位；另一个标志位是空闲的。
 *
 * 通常，nextOffset 链接到同一父节点的下一个元组（必须在
 * 同一页面上），如果没有下一个元组，则为 0。
 * 但是当根页面是叶子页面时，我们不会链接它的元组，
 * 因此在根页面上 nextOffset 总是为 0。
 *
 * 大小必须是 MAXALIGN 的倍数；此外，它必须至少为 SGDTSIZE，
 * 以便稍后可以将元组转换为 REDIRECT 状态。（这一限制仅在
 * 32 位机器上存储 NULL 叶子数据时增加字节；否则，对齐限制
 * 强制要求也能够做到这一点。）
 */
typedef struct SpGistLeafTupleData
{
	unsigned int tupstate:2,	/* 活动/重定向/死亡/占位符 */
				size:30;		/* 足够容纳任何可分配值的大小 */
	uint16		t_info;			/* nextOffset，链接到链中的下一个元组
								 * 以及两个标志位 */
	ItemPointerData heapPtr;	/* 表示堆元组的 TID */
	/* 如果其标志位被设置，则后面跟随 NULL 位图 */
	
/* 叶子数据，然后任何包含的数据，遵循在 MAXALIGN 边界上 */
} SpGistLeafTupleData;

/* 访问 nextOffset 和 t_info 内部位字段的宏 */
#define SGLT_GET_NEXTOFFSET(spgLeafTuple) \
	((spgLeafTuple)->t_info & 0x3FFF)
#define SGLT_GET_HASNULLMASK(spgLeafTuple) \
	(((spgLeafTuple)->t_info & 0x8000) ? true : false)
#define SGLT_SET_NEXTOFFSET(spgLeafTuple, offsetNumber) \
	((spgLeafTuple)->t_info = \
	 ((spgLeafTuple)->t_info & 0xC000) | ((offsetNumber) & 0x3FFF))
#define SGLT_SET_HASNULLMASK(spgLeafTuple, hasnulls) \
	((spgLeafTuple)->t_info = \
	 ((spgLeafTuple)->t_info & 0x7FFF) | ((hasnulls) ? 0x8000 : 0))

#define SGLTHDRSZ(hasnulls) \
	((hasnulls) ? MAXALIGN(sizeof(SpGistLeafTupleData) + \
						   sizeof(IndexAttributeBitMapData)) : \
	 MAXALIGN(sizeof(SpGistLeafTupleData)))
#define SGLTDATAPTR(x)		(((char *) (x)) + SGLTHDRSZ(SGLT_GET_HASNULLMASK(x)))
#define SGLTDATUM(x, s)		fetch_att(SGLTDATAPTR(x), \
									  (s)->attLeafType.attbyval, \
									  (s)->attLeafType.attlen)

/*
 * SPGiST 死元组：用于检查非活动元组的声明
 *
 * 该结构的 tupstate 字段必须与常规内部和叶子元组的匹配，
 * 它的大小字段必须与叶子元组的匹配。
 * 此外，指针字段必须与叶子元组的 heapPtr 字段位于同一位置，
 * 以满足我们在用死元组替换叶子元组时进行的一些断言。
 * 我们不使用 t_info，但它需要对齐指针字段。
 * 指针和 xid 仅在 tupstate = REDIRECT 时有效，并且在某些情况下，
 * xid 即使在那时也可以为 InvalidTransactionId；参见 initSpGistState。
 */
typedef struct SpGistDeadTupleData
{
	unsigned int tupstate:2,	/* 活动/重定向/死亡/占位符 */
				size:30;
	uint16		t_info;			/* 在死元组中未使用 */
	ItemPointerData pointer;	/* 索引内部的重定向 */
	TransactionId xid;			/* 插入此元组的事务的 ID */
} SpGistDeadTupleData;

typedef SpGistDeadTupleData *SpGistDeadTuple;

#define SGDTSIZE		MAXALIGN(sizeof(SpGistDeadTupleData))

/*
 * 用于进行空闲空间计算的宏。请注意，在计算元组所需的空间时，
 * 我们始终认为每个元组需要元组的大小加上 sizeof(ItemIdData) 
 * （用于行指针）。只要元组大小始终是最大对齐的，这样的计算是正确的。
 */

/* 在考虑固定头和特殊空间后页面的容量 */
#define SPGIST_PAGE_CAPACITY  \
	MAXALIGN_DOWN(BLCKSZ - \
				  SizeOfPageHeaderData - \
				  MAXALIGN(sizeof(SpGistPageOpaqueData)))

/*
 * 假设可以回收多达 n 个占位符（n 应为要插入的元组数量），
 * 计算页面上的空闲空间。
 */
#define SpGistPageGetFreeSpace(p, n) \
	(PageGetExactFreeSpace(p) + \
	 Min(SpGistPageGetOpaque(p)->nPlaceholder, n) * \
	 (SGDTSIZE + sizeof(ItemIdData)))

/*
 * XLOG 相关
 */

#define STORE_STATE(s, d)  \
	do { \
		(d).myXid = (s)->myXid; \
		(d).isBuild = (s)->isBuild; \
	} while(0)

/*
 * SpGistGetBuffer 的 "flags" 参数应该是 GBUF_LEAF 以获取叶页面，
 * 或 GBUF_INNER_PARITY(blockNumber) 以获取与指定块号在同一三重奇偶组的内部页面。
 * （通常，这应该是 GBUF_INNER_PARITY(parentBlockNumber + 1)
 * 以遵循 spgist/README 中描述的规则。）
 * 此外，可以将 GBUF_NULLS 进行 OR 操作以获取用于存储空值元组的页面。
 *
 * 注意：这些标志值用作 lastUsedPages 的索引。
 */
#define GBUF_LEAF				0x03
#define GBUF_INNER_PARITY(x)	((x) % 3)
#define GBUF_NULLS				0x04

#define GBUF_PARITY_MASK		0x03
#define GBUF_REQ_LEAF(flags)	(((flags) & GBUF_PARITY_MASK) == GBUF_LEAF)
#define GBUF_REQ_NULLS(flags)	((flags) & GBUF_NULLS)

/* spgutils.c */

/* reloption 参数 */
#define SPGIST_MIN_FILLFACTOR			10
#define SPGIST_DEFAULT_FILLFACTOR		80

extern SpGistCache *spgGetCache(Relation index);
extern TupleDesc getSpGistTupleDesc(Relation index, SpGistTypeDesc *keyType);
extern void initSpGistState(SpGistState *state, Relation index);
extern Buffer SpGistNewBuffer(Relation index);
extern void SpGistUpdateMetaPage(Relation index);
extern Buffer SpGistGetBuffer(Relation index, int flags,
							  int needSpace, bool *isNew);
extern void SpGistSetLastUsedPage(Relation index, Buffer buffer);
extern void SpGistInitPage(Page page, uint16 f);
extern void SpGistInitBuffer(Buffer b, uint16 f);
extern void SpGistInitMetapage(Page page);
extern unsigned int SpGistGetInnerTypeSize(SpGistTypeDesc *att, Datum datum);
extern Size SpGistGetLeafTupleSize(TupleDesc tupleDescriptor,
								   Datum *datums, bool *isnulls);
extern SpGistLeafTuple spgFormLeafTuple(SpGistState *state,
										ItemPointer heapPtr,
										Datum *datums, bool *isnulls);
extern SpGistNodeTuple spgFormNodeTuple(SpGistState *state,
										Datum label, bool isnull);
extern SpGistInnerTuple spgFormInnerTuple(SpGistState *state,
										  bool hasPrefix, Datum prefix,
										  int nNodes, SpGistNodeTuple *nodes);
extern SpGistDeadTuple spgFormDeadTuple(SpGistState *state, int tupstate,
										BlockNumber blkno, OffsetNumber offnum);
extern void spgDeformLeafTuple(SpGistLeafTuple tup, TupleDesc tupleDescriptor,
							   Datum *datums, bool *isnulls,
							   bool keyColumnIsNull);
extern Datum *spgExtractNodeLabels(SpGistState *state,
								   SpGistInnerTuple innerTuple);
extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page,
										 Item item, Size size,
										 OffsetNumber *startOffset,
										 bool errorOK);
extern bool spgproperty(Oid index_oid, int attno,
						IndexAMProperty prop, const char *propname,
						bool *res, bool *isnull);

/* spgdoinsert.c */
extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN,
							  BlockNumber blkno, OffsetNumber offset);
extern void spgPageIndexMultiDelete(SpGistState *state, Page page,
									OffsetNumber *itemnos, int nitems,
									int firststate, int reststate,
									BlockNumber blkno, OffsetNumber offnum);
extern bool spgdoinsert(Relation index, SpGistState *state,
						ItemPointer heapPtr, Datum *datums, bool *isnulls);

/* spgproc.c */
extern double *spg_key_orderbys_distances(Datum key, bool isLeaf,
										  ScanKey orderbys, int norderbys);
extern BOX *box_copy(BOX *orig);

#endif							/* SPGIST_PRIVATE_H */
